Skip to content

Run released workflows with the current run, resume, revise and verify (#271) - #289

Merged
memtomem merged 3 commits into
mainfrom
test/frozen-workflows2-271
Nov 9, 2026
Merged

memtomem merged 3 commits into
mainfrom
test/frozen-workflows2-271

Conversation

@memtomem

@memtomem memtomem commented Nov 9, 2026

Copy link
Copy Markdown
Owner

Refs #271. This is the Workflows family, part 2a. Part 1 (#287) froze v0.2.0 and v0.3.0 workflow stores written by each release's own CLI and read them with the current readers. This part runs the current run, resume, revise and apply --verify over those same stores. The issue stays open.

What

Tests only: no fixture bytes and no source under src/ change.

  • How it runs. Execution goes through WorkflowCoordinator.run, the call the durable job's worker makes; the hermetic suite starts no job. Fake codex and kimi-code node agents record their prompts. The workflow run / resume CLI is tested up to a stubbed job launch.
  • run of released planned, created and revised rows (a revision leaves its parent runnable):
    • each node gets the stored goal and its own stored prompt, never the other node's task; the final node also gets its dependency's output;
    • calls_used continues from the released count;
    • the result is a current final artifact, approved, whose patch and bundle digests are recomputed from the final node's stored bytes;
    • every released workflow file (planner output, spec) and event is kept as written.
  • resume against the stored absolute deadline. The clock is fixed from the row's own deadline_at, because three of these rows were stored without a heartbeat.
    • After the deadline: every crash and failure ends workflow deadline exceeded before any dispatch, with the deadline unchanged.
    • Before it, crashes: a dead owner's run and a recovered one reuse the released draft bundle and refund the killed node's attempt (3 → 4 calls). The released events continue in sequence.
    • Before it, a published final artifact: it is reused and approved, and v0.2.0's is not backfilled with final_agent. It is refused, without approving, replacing or re-running anything, when its patch bytes, plan digests, source fingerprint, node, workflow or repository disagree.
    • Before it, exhausted nodes: nothing is dispatched, and the run ends no runnable nodes remain before final_node completed.
    • Refusals: from completed, cancelled and planned, and while the released owner's pid lives. A refused takeover appends a failed resume execution after the released history and changes nothing else.
    • Output: the completion is judged by this invocation's own execution, even when the row's state or a later execution says otherwise.
  • workflow revise: a v0.2.0 parent gains a lineage, its only changed column; a v0.3.0 lineage continues past its last version.
  • The launch: the team peek follows default_team for a v0.2.0 row and not for a v0.3.0 row that recorded none. A final state is refused before any launch.
  • apply --verify of the released final artifact, also after a prune:
    • v0.2.0 gets the gate's own allowlist only, with the verify: notice;
    • v0.3.0 also gets the final agent's list;
    • an unlisted variable never arrives, and the common ones do.
  • Control plans enabled refuse a released workflow planned without one, before any agent or Toolgraph call.
  • docs/architecture/persisted-contracts.md: the Workflows row names these.

No current reader or executor misread a released workflow.

Part 2b (not here; still "not yet" in the row). Each needs new release-written fixtures:

  • control-plan workflows written by a release;
  • a node bundle saved just before a crash, reclaimed on resume (the released crash fixtures have no bundle for the killed node, so this part covers completed-dependency reuse only);
  • recovery of an interrupted workflow-origin apply.

Verification

  • suite-run CLEAN; ruff check . clean.
  • 24 injected regressions in workflow.py and cli.py each fail an intended assertion. They include:
    • the stored deadline reset;
    • no refund, or a refund for a durable failure;
    • the live-lease check, execution history or approval dropped;
    • the control-plan refusal removed, or an advisory call before it;
    • the lineage, team and verify-agent rules broken;
    • invocation() returning nothing or the latest entry;
    • the plan digests dropped;
    • each artifact-reuse guard removed;
    • the planner output truncated;
    • either node's task leaking into the other's prompt.
  • Codex reviewed the design in two rounds before any code. The implementation went three rounds:
    • Round 1: NEEDS-FIX with 4 Major, fixed in 3e40cda.
      • The Toolgraph seam raised an exception that run_preflight swallows.
      • Digests, invocation and released files were unchecked.
    • Round 2: NEEDS-FIX with 2 Major and 1 Minor, fixed in 0cf71ad.
      • The invocation check from round 1 equated this invocation with the last entry.
      • The artifact-reuse guards were unexercised.
    • Round 3: unprimed, given the diff only. SHIP with no findings.

🤖 Generated with Claude Code

memtomem and others added 3 commits November 9, 2026 23:45
#271)

The Workflows family of the persisted-contracts D rows, part 2a. No new
fixtures and no src change: the v0.2.0 and v0.3.0 stores frozen in part 1
are now executed by HEAD.

Execution goes through WorkflowCoordinator.run, the call the durable job's
worker makes; the hermetic suite starts no job. Fake codex and kimi-code
node agents record their prompts.
- run of released planned, created and revised rows (a revision leaves
  its parent runnable): each node gets the stored goal and its own stored
  prompt, the final node its dependency's output; calls_used continues
  from the released count; a HEAD final artifact, approved.
- resume against the stored absolute deadline, with the clock fixed from
  the row's own deadline_at (three of these rows were stored without a
  heartbeat). After it: every crash and failure ends "workflow deadline
  exceeded" before any dispatch, deadline unchanged. Before it: a dead
  owner's run and a recovered one reuse the released draft bundle and
  refund the killed node's attempt (3 -> 4 calls); a final artifact
  published before the crash is reused and approved, and v0.2.0's is not
  backfilled with final_agent; an exhausted node dispatches nothing.
  Refused from completed, cancelled and planned, and while the released
  owner's pid lives, where the refused attempt is appended after the
  released execution history. Events continue the released sequence.
- workflow revise: a v0.2.0 parent gains a lineage (its only changed
  column), a v0.3.0 lineage continues past its last version.
- workflow run/resume up to a stubbed launch: the team peek follows
  default_team for a v0.2.0 row and not for a v0.3.0 row that recorded
  none; a final state is refused before any launch.
- apply --verify of the released final artifact: v0.2.0 gets the gate's
  own allowlist only, with the notice; v0.3.0 also the final agent's;
  both also after a prune.
- control plans enabled refuse a released workflow planned without one,
  before any agent or Toolgraph call.

The doc row names these; still not yet: release-written control-plan
workflows, a bundle saved just before a crash, and an interrupted
workflow-origin apply (part 2b, new fixtures).

13 injected regressions in workflow.py and cli.py each fail a test here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 1 found assertions that survived the code they protect:
- rendering is now judged by the invocation's own execution: it must be
  the one appended after the released history, and the same record with
  its state flipped must print the same outcome (invocation() -> None
  passed before);
- the final artifact's patch and bundle digests are recomputed from the
  final node's stored bytes (dropping both from the plan passed before);
- the control-plan refusal records Toolgraph calls instead of raising
  from the seam, which run_preflight swallows (an advisory call before
  the refusal passed before);
- run and revise keep every released workflow file and event as
  written (a truncated planner output passed before);
- apply --verify: an unlisted variable never reaches the gate, the
  common ones and the listed values do.

17 injected regressions now each fail a test here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
Review round 2:
- rendering is checked against a later, conflicting execution appended
  to the same record: the outcome stays this invocation's (returning the
  latest execution passed before, because the round-1 check equated this
  invocation with the last entry);
- resume of the released published final artifact is refused, without
  approving, replacing or re-running anything, when its patch bytes,
  plan digests, source fingerprint, node, workflow or repository disagree
  (the reuse checks could be removed before);
- the final node's prompt never carries the draft node's task.

24 injected regressions now each fail a test here.

Co-Authored-By: Claude Opus 5.5 <noreply@anthropic.com>
@memtomem
memtomem merged commit 4fa0f7c into main Nov 9, 2026
11 checks passed
@memtomem
memtomem deleted the test/frozen-workflows2-271 branch November 10, 2026 02:47
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

1 participant